#ifndef __INETADDR_H__
#define __INETADDR_H__

#include <string>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>

constexpr socklen_t kInAddrLen = sizeof(sockaddr_in);
constexpr socklen_t kIn6AddrLen = sizeof(sockaddr_in6);

/**
 * @brief A universal sockaddr struct union
 */
union uniAddr
{
    sockaddr sa;
    sockaddr_in sin;
    sockaddr_in6 sin6;
};

/**
 * @brief Value type Socket Address struct encapsulation
 */
struct SocketAddr
{
    SocketAddr() : addr_{}
    {
    }

    /**
     * @brief Convert ipstr:port to universal addr struct
     * @param ipstr
     * @param port
     */
    SocketAddr(const char* ipstr, in_port_t port, bool ipv6 = false);

    /**
     * @brief Explicit Convert from sockaddr to universal addr struct
     *
     * @param sa
     */
    explicit SocketAddr(const sockaddr& sa);

    /**
     * @brief Explicit Convert from sockaddr_in to universal addr struct
     * @param sin
     */
    explicit SocketAddr(const sockaddr_in& sin);

    /**
     * @brief Explicit Convert from sockaddr_in6 to universal addr struct
     * @param sin6
     */
    explicit SocketAddr(const sockaddr_in6& sin6);

    static SocketAddr getLocalAddr(int sockfd);

    static SocketAddr getPeerAddr(int sockfd);

    /**
     * @brief Get AF_INET or AF_INET6
     * @return sa_family_t
     */
    sa_family_t family() const
    {
        return addr_.sa.sa_family;
    }

    /**
     * @brief Get port from sockaddr in netbit value
     * @return in_port_t
     */
    in_port_t port() const
    {
        return addr_.sin.sin_port;
    }

    /**
     * @brief Get in from sockaddr in netbit value
     * @return in_addr
     */
    in_addr ipv4() const;

    /**
     * @brief Get in6_addr struct from sockaddr
     * @return in_addr_t
     */
    in6_addr ipv6() const;

    /**
     * @brief Set the addr_ member
     * @param  addr addr to set
     * @param  socklen the length of addr to be set
     */
    void setAddr(const sockaddr* addr, socklen_t socklen);

    /**
     * @brief Get a string of ip
     * @return std::string  "192.168.16.1"
     */
    std::string toString() const;

    /**
     * @brief Get a string of ip:port
     *
     * @return std::string e.g. "192.168.16.1:8888"
     */
    std::string toStringPort() const;

    /**
     * @brief Check if this IP is an intranet ip
     * @return true
     * @return false
     */
    bool isIntraNetIP() const;

    /**
     * @brief Get an SocketObject for loopback addr
     * 127.0.0.1
     * @param port Port number for lo
     * @param ipv6 Is IPv6 addr
     * @return SocketAddr
     */
    static SocketAddr loopbackAddr(in_port_t port,
                                   bool loopbackOnly = false,
                                   bool ipv6 = false);

    /**
     * @brief Return const pointer to sockaddr
     */
    sockaddr* sa()
    {
        return &addr_.sa;
    }

    bool isSameIP(const SocketAddr& that) const
    {
        if (addr_.sa.sa_family == AF_INET)
            return !memcmp(&addr_.sin.sin_addr,
                           &that.addr_.sin.sin_addr.s_addr,
                           sizeof(addr_.sin.sin_addr));
        else
            return !memcmp(&addr_.sin6.sin6_addr,
                           &that.addr_.sin6.sin6_addr,
                           sizeof(addr_.sin6.sin6_addr));
    }

    bool isSamePort(const SocketAddr& that) const
    {
        return port() == that.port();
    }

    bool operator==(const SocketAddr& that) const
    {
        return isSameIP(that) && isSamePort(that);
    }

    uniAddr addr_;
};

#endif  // __INETADDR_H__